﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text.RegularExpressions;
using ConfigGen.Core.ConfigurationSettings;
using log4net;

namespace ConfigGen.Core
{
    /// <summary>
    /// Responsible for filtering the full MachineConfigurationSettingCollection list of settings, according to the user's supplied settings.
    /// </summary>
    public class MachineConfigurationSettingCollectionFilter : IMachineConfigurationSettingCollectionFilter
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(MachineConfigurationSettingCollectionFilter));
        private readonly string _localMachineName;

        /// <summary>
        /// Gets the name of the default machine; this is the "machine name" entry in the spreadsheet file which is to be treated as 
        /// the local machine if no row's machine name matches the local machine. I.e. if run on a local machine "RobDev" with the switch "local only",
        /// the configuration file generator will first look for a row for machine "RobDev" and if that does not exist, it will look for a row with 
        /// the machine name matching this property.
        /// </summary>
        public const string DefaultMachineName = "default";

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineConfigurationSettingCollectionFilter"/> class, taking the supplied parameter as the local machine name.
        /// </summary>
        /// <param name="localMachineName">Name of the local machine.</param>
        /// <exception cref="ArgumentNullException">Raised if <paramref name="localMachineName"/> is null.</exception>
        /// <exception cref="ArgumentException">Raised if <paramref name="localMachineName"/> is zero length.</exception>
        public MachineConfigurationSettingCollectionFilter(string localMachineName)
        {
            if (localMachineName == null) throw new ArgumentNullException("localMachineName");
            if (localMachineName.Length == 0) throw new ArgumentException("localMachineName");
            _localMachineName = localMachineName;
        }

        /// <summary>
        /// Filters the supplied list of machine configurations, according to the supplied settings.
        /// </summary>
        /// <remarks>
        /// <para>If <see cref="Preferences.LocalOnly"/> is specified in the supplied settings argument, then the list of machine configurations will be filtered to
        /// one that matches the current machine name, if it exists, otherwise the one for a machine with the name given by <see cref="DefaultMachineName"/>. If this option is specified
        /// but neither condition is met, an exception is thrown.</para>
        /// <para>If <see cref="Preferences.LocalOnly"/> is not specified, the list is unfiltered.</para>
        /// </remarks>
        /// <param name="machineSettings">List of machine configuration settings (<see cref="MachineConfigurationSettings"/>) from the spreadsheet</param>
        /// <param name="settings">Configuration generation settings for current config gen "run".</param>
        /// <returns>Filtered list of <see cref="MachineConfigurationSettings"/>.</returns>
        /// <exception cref="ConfigurationErrorsException">Raised if <see cref="Preferences.LocalOnly"/> is specified but no entry found in supplied list
        /// for either the local machine's name, nor the default machine name.</exception>
        public IList<MachineConfigurationSettings> Filter(IEnumerable<MachineConfigurationSettings> machineSettings, Preferences settings)
        {
            if (machineSettings == null) throw new ArgumentNullException("machineSettings");
            if (settings == null) throw new ArgumentNullException("settings");

            if (settings.LocalOnly)
            {
                Log.Debug("Filtering loaded spreadsheet as \"LocalOnly\" switch was supplied");
                var setting = machineSettings.FirstOrDefault( s => string.Equals(_localMachineName, s.MachineName, StringComparison.OrdinalIgnoreCase));
                if (setting != null)
                {
                    Log.DebugFormat("Returning spreadsheet row for '{0}'", setting.MachineName);
                    return new List<MachineConfigurationSettings> {setting};
                }

                Log.DebugFormat("No spreadsheet row for '{0}'", _localMachineName);
                setting = machineSettings.FirstOrDefault( s => string.Equals(DefaultMachineName, s.MachineName, StringComparison.OrdinalIgnoreCase));
                if (setting != null)
                {
                    Log.DebugFormat("Returning spreadsheet row for '{0}'", setting.MachineName);
                    return new List<MachineConfigurationSettings> { setting };
                }
                Log.DebugFormat("No spreadsheet row for '{0}'", DefaultMachineName);

                var msg = string.Format(
                        "The 'local-only' option was specified, but the settings spreadsheet did not contain a row for the current machine name ({0}), nor a row for the default machine ({1})",
                        _localMachineName,
                        DefaultMachineName);
                throw new ConfigurationErrorsException(msg);
            }

            if (!string.IsNullOrEmpty(settings.MachineFilterExpression))
            {
                if (settings.SpecifiedMachines != null)
                {
                    throw new ConfigurationErrorsException("Both the 'filter-machines-regex' and 'generate-specified-only' options were specified, but only one of these is allowed");
                }
                return machineSettings.Where(m => Regex.IsMatch(m.MachineName, settings.MachineFilterExpression)).ToList();
            }

            if (settings.SpecifiedMachines != null
                && settings.SpecifiedMachines.Length > 0)
            {
                return machineSettings.Where(settingsCollection => settings.SpecifiedMachines.Contains(settingsCollection.MachineName, StringComparer.OrdinalIgnoreCase)).ToList();
            }

            return machineSettings.ToList();
        }
    }
}
